<?php

declare(strict_types=1);

namespace app\util;

class OrgCodeUtil
{
    // 4位超过456976会报错，应该也没有那么多商户。。。
    // 常量定义
    const CODE_LENGTH = 4;
    const CODE_SPACE = 456976; // 26^4
    const PRIME_A = 31;
    const PRIME_B = 17;

    /**
     * 根据组织ID和日期生成4位大写英文字母编码（完全无存储）
     * @param int $organizationId 组织ID
     * @param string $date 日期格式 Y-m-d
     * @return string 4位大写英文字母编码
     */
    public static function generateCode($organizationId, $date = null)
    {
        if (!$date) {
            $date = date('Y-m-d');
        }

        // 将日期转换为数字
        $dateNum = strtotime($date) % 100000; // 限制日期数字范围

        // 使用线性同余生成器生成唯一的种子
        // f(x) = (a * x + b * y) mod m
        // 其中 a=31, b=17, m=456976
        $seed = ($organizationId * self::PRIME_A + $dateNum * self::PRIME_B) % self::CODE_SPACE;

        // 将数字转换为4位大写英文字母
        return self::numToAlpha($seed, self::CODE_LENGTH);
    }

    /**
     * 数字转大写英文字母
     * @param int $num 数字
     * @param int $length 长度
     * @return string
     */
    public static function numToAlpha($num, $length)
    {
        $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $result = '';

        for ($i = 0; $i < $length; $i++) {
            $result = $characters[$num % 26] . $result;
            $num = intval($num / 26);
        }

        return $result;
    }

    /**
     * 大写英文字母转数字
     * @param string $alpha 大写英文字母
     * @return int
     */
    public static function alphaToNum($alpha)
    {
        $num = 0;
        $length = strlen($alpha);

        for ($i = 0; $i < $length; $i++) {
            $char = $alpha[$i];
            $num = $num * 26 + (ord($char) - ord('A'));
        }

        return $num;
    }

    /**
     * 验证编码是否有效
     * @param string $code 4位大写英文字母编码
     * @return bool
     */
    public static function isValidCode($code)
    {
        return preg_match('/^[A-Z]{4}$/', $code) === 1;
    }

    /**
     * 扩展欧几里得算法求逆元
     * @param int $a 
     * @param int $m 模数
     * @return int 逆元
     */
    private static function modInverse($a, $m)
    {
        $m0 = $m;
        $y = 0;
        $x = 1;

        if ($m == 1)
            return 0;

        while ($a > 1) {
            // q 是 a/m 的商
            $q = intval($a / $m);

            $t = $m;

            // m 是余数，现在成为下一个除数
            $m = $a % $m;
            $a = $t;
            $t = $y;

            // 更新 x 和 y
            $y = $x - $q * $y;
            $x = $t;
        }

        // 确保 x 是正数
        if ($x < 0)
            $x += $m0;

        return $x;
    }

    /**
     * 根据编码和日期精确反推组织ID（完全无存储且精确）
     * @param string $code 4位大写英文字母编码
     * @param string $date 日期格式 Y-m-d
     * @return int|null 组织ID
     */
    public static function getOrganizationIdByCode($code, $date = null)
    {
        if (!$date) {
            $date = date('Y-m-d');
        }

        // 将编码转换为数字
        $codeNum = self::alphaToNum($code);

        // 将日期转换为数字
        $dateNum = strtotime($date) % 100000;

        // 解线性同余方程求组织ID
        // codeNum ≡ (organizationId * PRIME_A + dateNum * PRIME_B) (mod CODE_SPACE)
        // organizationId * PRIME_A ≡ (codeNum - dateNum * PRIME_B) (mod CODE_SPACE)
        // organizationId ≡ (codeNum - dateNum * PRIME_B) * MOD_INV_A (mod CODE_SPACE)

        $rightSide = ($codeNum - $dateNum * self::PRIME_B) % self::CODE_SPACE;
        if ($rightSide < 0) {
            $rightSide += self::CODE_SPACE;
        }

        // 计算 PRIME_A 在模 CODE_SPACE 下的逆元
        $modInvA = self::modInverse(self::PRIME_A, self::CODE_SPACE);

        $organizationId = ($rightSide * $modInvA) % self::CODE_SPACE;

        // 验证结果是否正确
        if (self::generateCode($organizationId, $date) === $code) {
            return $organizationId;
        }

        // 如果直接计算不匹配，可能是由于哈希冲突或计算误差，尝试在小范围内查找
        for ($i = 1; $i <= 1000; $i++) {
            $testId = $organizationId + $i;
            if (self::generateCode($testId, $date) === $code) {
                return $testId;
            }

            $testId = $organizationId - $i;
            if ($testId > 0 && self::generateCode($testId, $date) === $code) {
                return $testId;
            }
        }

        return null;
    }

    /**
     * 批量验证编码（用于测试）
     * @param int $maxOrgId 最大组织ID
     * @param string $date 日期
     * @return bool 是否所有编码都能正确反推
     */
    public static function validateCodes($maxOrgId = 100, $date = null)
    {
        if (!$date) {
            $date = date('Y-m-d');
        }

        for ($orgId = 1; $orgId <= $maxOrgId; $orgId++) {
            $code = self::generateCode($orgId, $date);
            $decodedOrgId = self::getOrganizationIdByCode($code, $date);

            if ($decodedOrgId !== $orgId) {
                echo "验证失败: OrgId={$orgId}, Code={$code}, Decoded={$decodedOrgId}\n";
                return false;
            }
        }

        return true;
    }
}
